package com.jeesite.modules.dataScreen.service;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import com.jeesite.modules.dataScreen.constants.CacheConstants;
import com.jeesite.modules.dataScreen.constants.MonitoringConstants;
import com.jeesite.modules.dataScreen.dao.MonitoringDao;
import com.jeesite.modules.dataScreen.domain.monitoring.*;
import com.jeesite.modules.dataScreen.utils.MyDateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class MonitoringService extends ServiceImpl<MonitoringDao, PublicOpinionMonitoring> {


    @Resource
    private MonitoringDao monitoringDao;

    @Autowired
    @Lazy
    private MonitoringService monitoringService;

    @Autowired
    private IndustryIndexRealtimeYqService industryIndexRealtimeYqService;

    private static final String MONITORING_MONTH_CACHE = "monitoring:month:cache";

    private static final String MONITORING_DATE_CACHE = "monitoring:date:cache";

    //民情民意-舆情月数据cache
    private final Cache<String, MonitoringMonthInfoVo> monthMonitoringCache = CacheBuilder.newBuilder().build();

    //民情民意-舆情当日数据cache
    private final Cache<String, MonitoringDateInfoVo> dateMonitoringCache = CacheBuilder.newBuilder().build();

    //民情民意-月数据映射
    private static final Map<String, Function<MonitoringMonthInfoVo, String>> MONTH_INFO_MAP = new HashMap<>();

    //民情民意-日数据映射
    private static final Map<String, Function<MonitoringDateInfoVo, String>> DATE_INFO_MAP = new HashMap<>();

    static {
        DATE_INFO_MAP.put("当日信息总量", (o) -> convertToString(o.getPublicOpinionTotalNum()));
        DATE_INFO_MAP.put("当日舆情指数", (o) -> convertToString(o.getPublicOpinionIndex()));
        DATE_INFO_MAP.put("当日滚动播报", (o) -> convertToString(o.getDailyDetailList()));
        DATE_INFO_MAP.put("当日央级媒体报道信息", (o) -> convertToString(o.getCentralMediaNum()));
        DATE_INFO_MAP.put("当日省级媒体报道信息", (o) -> convertToString(o.getProvincialDetailList()));

        MONTH_INFO_MAP.put("近一个月平台排行榜", (o) -> convertToString(o.getWebRankList()));
        MONTH_INFO_MAP.put("近一个月舆情趋势图", (o) -> convertToString(o.getDailyDataList()));
        MONTH_INFO_MAP.put("近一个月媒体分布图", (o) -> convertToString(o.getSourceTypeList()));
        MONTH_INFO_MAP.put("近一个月区县排行榜", (o) -> convertToString(o.getDistrictRanklList()));
    }

    public List<MonitoringSimple> getMonitoringSimpleList(String classifyName) {
        DateTime[] dateTimes = MyDateUtils.getCurrentDay();
        String[] codeParams;
        if (StrUtil.isNotBlank(classifyName)) {
            codeParams = MonitoringItemTyp.getNameByCode(classifyName);
        } else {
            codeParams = MonitoringItemTyp.getAllNames();
        }

        List<MonitoringSimple> simpleList = monitoringDao.getMonitoringSimpleList(dateTimes[0], dateTimes[1], codeParams);
//        for (MonitoringSimple simple : simpleList) {
//            simple.setSourceType(MonitoringSourceTye.toName(simple.getSourceType()));
//        }
        return simpleList;
    }

    public Map<String, Object> getItemCount() {
        Map<String, Object> result = new TreeMap<>();
        DateTime[] dateTimes = MyDateUtils.getCurrentDay();
        List<JSONObject> itemCounts = monitoringDao.getItemCountMap(dateTimes[0], dateTimes[1]);
        Map<String, Long> tmp = new TreeMap<>();
        long total = 0l;
        for (JSONObject itemCount : itemCounts) {
            long val = itemCount.getLongValue("val");
            tmp.put(itemCount.getString("classifyname"), val);
        }
        for (MonitoringItemTyp itemType : MonitoringItemTyp.values()) {
            long v = 0;
            for (String name : itemType.getNames()) {
                v += tmp.getOrDefault(name, 0L);
            }
            result.put(itemType.getCode(), v);
            total += v;
        }
        result.put("total", total);
        return result;
    }

    @Cacheable(value = CacheConstants.DEFAULT, key = "'monitoringDetail'+#id")
    public MonitoringDetailVo detail(String id) {
        MonitoringDetailVo detailVo = monitoringDao.getOneById(id);
        if (Objects.nonNull(detailVo)) {
            String content = detailVo.getContent();
            if (StrUtil.isNotBlank(content)) {
                String string = HtmlUtil.cleanHtmlTag(content);
                String subBefore = StrUtil.subBefore(string, "beginimg", false);
                detailVo.setContent(subBefore);
            }
            detailVo.setSourceType(MonitoringSourceTye.toName(detailVo.getSourceType()));
        }
        return detailVo;
    }


    /**
     * 查询当日数据cache  不是当日数据查询数据库
     *
     * @param publishDate
     * @return
     */
    public MonitoringDateInfoVo listMonitoringByPublishDateCache(String publishDate) {
        String today = DateUtil.today();
        try {
            if (StringUtils.equals(today, publishDate)) {
                return dateMonitoringCache.get(MONITORING_DATE_CACHE, () -> monitoringService.listMonitoringByPublishDate(today));
            }
        } catch (Exception e) {
            log.error("listMonitoringByPublishDateCache error", e);
            return monitoringService.listMonitoringByPublishDate(publishDate);
        }
        return monitoringService.listMonitoringByPublishDate(publishDate);
    }


    /**
     * 查询当月数据cache
     *
     * @return
     */
    public MonitoringMonthInfoVo getMonitoringMonthInfoByCache() {
        try {
            return monthMonitoringCache.get(MONITORING_MONTH_CACHE, () -> monitoringService.getMonitoringMonthInfo());
        } catch (ExecutionException e) {
            log.error("getMonitoringMonthInfoByCache error", e);
            return null;
        }
    }


    /**
     * 查询民情民意日数据
     *
     * @param publishDate
     * @return
     */
    public MonitoringDateInfoVo listMonitoringByPublishDate(String publishDate) {

        MonitoringDateInfoVo monitoringDateInfoVo = new MonitoringDateInfoVo();

        //查询当天发布的数据
        Date date = DateUtil.parse(publishDate, "yyyy-MM-dd");
        LocalDateTime startOfDay = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().with(LocalTime.MIN);
        LocalDateTime endOfDay = startOfDay.with(LocalTime.MAX);
        LambdaQueryWrapper<PublicOpinionMonitoring> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.ge(PublicOpinionMonitoring::getPublishTime, startOfDay)
                .le(PublicOpinionMonitoring::getPublishTime, endOfDay);
        List<PublicOpinionMonitoring> monitoringList = this.list(queryWrapper);
        if (CollectionUtils.isEmpty(monitoringList)) {
            return monitoringDateInfoVo;
        }

        List<MonitoringDetailVo> monitoringDetailVos = new ArrayList<>();
        for (int i = 0; i < monitoringList.size(); i++) {
            MonitoringDetailVo monitoringDetailVo = new MonitoringDetailVo();
            PublicOpinionMonitoring monitoring = monitoringList.get(i);
            BeanUtils.copyProperties(monitoring, monitoringDetailVo);
            monitoringDetailVo.setIndex(i + 1);
            monitoringDetailVos.add(monitoringDetailVo);
        }

        //舆情总量
        monitoringDateInfoVo.setPublicOpinionTotalNum(monitoringList.size());
        //央级媒体信息数
        int provincialMediaSize = 0;
        //省级媒体信息数
        int centralMediaSize = 0;
        //央级媒体舆情信息集合
        List<MonitoringDetailVo> centralDetailList = new ArrayList<>();
        //省级媒体舆情信息集合
        List<MonitoringDetailVo> provincialDetailList = new ArrayList<>();


        for (MonitoringDetailVo monitoringDetailVo : monitoringDetailVos) {
            String author = monitoringDetailVo.getAuthor();
            if (MonitoringConstants.centralMediaList.contains(author)) {
                centralMediaSize++;
                MonitoringDetailVo mediaVo = new MonitoringDetailVo();
                BeanUtils.copyProperties(monitoringDetailVo, mediaVo);
                mediaVo.setIndex(centralMediaSize);
                centralDetailList.add(mediaVo);
            }
            if (MonitoringConstants.provincialMediaList.contains(author)) {
                provincialMediaSize++;
                MonitoringDetailVo provincialVo = new MonitoringDetailVo();
                BeanUtils.copyProperties(monitoringDetailVo, provincialVo);
                provincialVo.setIndex(provincialMediaSize);
                provincialDetailList.add(provincialVo);
            }
        }

        monitoringDateInfoVo.setCentralMediaNum(centralMediaSize);
        monitoringDateInfoVo.setProvincialMediaNum(provincialMediaSize);
        monitoringDateInfoVo.setCentralDetailList(centralDetailList);
        monitoringDateInfoVo.setProvincialDetailList(provincialDetailList);
        monitoringDateInfoVo.setDailyDetailList(monitoringDetailVos);
        monitoringDateInfoVo.setPublicOpinionIndex(centralMediaSize + provincialMediaSize + monitoringList.size());

        return monitoringDateInfoVo;
    }

    /**
     * 查询查询民情民意日数据
     * @return
     */
    public MonitoringMonthInfoVo getMonitoringMonthInfo() {
        long startTime = System.currentTimeMillis();
        MonitoringMonthInfoVo monitoringMonthInfoVo = new MonitoringMonthInfoVo();

        //获取近30天数据
        Date beginDate = new Date();
        Date endDate = this.addDays(beginDate, -30);
        QueryWrapper<PublicOpinionMonitoring> queryWrapper = new QueryWrapper<PublicOpinionMonitoring>();
        queryWrapper.select("classify_name", "web_name", "source_type", "key_word", "publish_time")
                .lambda()
                .lt(PublicOpinionMonitoring::getPublishTime, beginDate)
                .gt(PublicOpinionMonitoring::getPublishTime, endDate);
        List<PublicOpinionMonitoring> monitoringList = this.list(queryWrapper);

        ///按行业统计
        CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(() -> {
            Map<String, Integer> classifyNameMap = countByFunction(monitoringList, PublicOpinionMonitoring::getClassifyName);
            List<RankVO> rankVOS = mapToRankVo(classifyNameMap, 10);
            monitoringMonthInfoVo.setClassifyRankList(rankVOS);
        }).exceptionally(e -> {
            log.error("setClassifyRankList error", e);
            return null;
        });

        //按平台统计
        CompletableFuture<Void> completableFuture2 = CompletableFuture.runAsync(() -> {
            Map<String, Integer> classifyNameMap = countByFunction(monitoringList, PublicOpinionMonitoring::getWebName);
            List<RankVO> rankVOS = mapToRankVo(classifyNameMap, 10);
            monitoringMonthInfoVo.setWebRankList(rankVOS);
        }).exceptionally(e -> {
            log.error("setWebRankList error", e);
            return null;
        });

        //按媒体类型统计
        CompletableFuture<Void> completableFuture3 = CompletableFuture.runAsync(() -> {
            Map<String, Integer> classifyNameMap = countByFunction(monitoringList, PublicOpinionMonitoring::getSourceType);
            List<RankVO> rankVOS = mapToRankVo(classifyNameMap, 10);
            monitoringMonthInfoVo.setSourceTypeList(rankVOS);
        }).exceptionally(e -> {
            log.error("setSourceTypeList error", e);
            return null;
        });

        //按区县和日期统计
        CompletableFuture<Void> completableFuture4 = CompletableFuture.runAsync(() -> {
            //区县集合
            List<PublicOpinionMonitoring> districtList = new ArrayList<>();
            //日期map
            Map<String, Integer> dateMap = new HashMap<>();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            monitoringList.forEach(o -> {
                String keyWord = o.getKeyWord();
                //按天获取日期
                Date publishTime = o.getPublishTime();
                LocalDate enterDate = publishTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                String formattedDate = enterDate.format(formatter);

                //区县需要根据关键字挨个匹配
                if (StringUtils.isNotBlank(keyWord)) {
                    String[] split = keyWord.split(",");
                    for (String str : split) {
                        if (MonitoringConstants.districtList.contains(str)) {
                            PublicOpinionMonitoring publicOpinionMonitoring = new PublicOpinionMonitoring();
                            BeanUtils.copyProperties(o, publicOpinionMonitoring);
                            publicOpinionMonitoring.setKeyWord(str);
                            districtList.add(publicOpinionMonitoring);
                            break;
                        }
                    }
                }
                dateMap.merge(formattedDate, 1, Integer::sum);
            });

            Map<String, Integer> districtMap = countByFunction(districtList, PublicOpinionMonitoring::getKeyWord);
            List<RankVO> rankVOS = mapToRankVo(districtMap, 5);
            monitoringMonthInfoVo.setDistrictRanklList(rankVOS);
            List<RankVO> rankVOS1 = mapToRankVo(dateMap, 31);
            rankVOS1 = rankVOS1.stream()
                    .sorted(Comparator.comparing(rankVO -> LocalDate.parse(rankVO.getText(), formatter)))
                    .collect(Collectors.toList());
            monitoringMonthInfoVo.setDailyDataList(rankVOS1);
        }).exceptionally(e -> {
            log.error("setDistrictRanklList error", e);
            return null;
        });

        CompletableFuture.allOf(completableFuture1, completableFuture2, completableFuture3, completableFuture4).join();
        long endTime = System.currentTimeMillis();
        long elapsedTime = endTime - startTime;
        log.info("Method adb completed in {} milliseconds.", elapsedTime);
        return monitoringMonthInfoVo;
    }


    /**
     * 定时同步数据到底座及cache
     */
    @Scheduled(fixedRate = 310000) // 同步速率 5分钟多一点
    public void scheduledRefreshCache() {
        MonitoringMonthInfoVo monitoringMonthInfoVo = monitoringService.getMonitoringMonthInfo();
        log.info("MonitoringMonthInfoVo cache refresh: {}", JSON.toJSONString(monitoringMonthInfoVo));

        //同步月数据到底座
        List<IndustryIndexRealtimeYq> indexRealtimeYqs = new ArrayList<>();
        MONTH_INFO_MAP.forEach((k, v) -> {
            IndustryIndexRealtimeYq industryIndexRealtimeYq = IndustryIndexRealtimeYq.builder()
                    .indicatorName(k)
                    .indicatorValue(MONTH_INFO_MAP.get(k).apply(monitoringMonthInfoVo))
                    .build();
            indexRealtimeYqs.add(industryIndexRealtimeYq);
        });

        //同步当日数据到底座
        String today = DateUtil.today();
        MonitoringDateInfoVo monitoringDateInfoVo = monitoringService.listMonitoringByPublishDate(today);

        DATE_INFO_MAP.forEach((k, v) -> {
            IndustryIndexRealtimeYq industryIndexRealtimeYq = IndustryIndexRealtimeYq.builder()
                    .indicatorName(k)
                    .indicatorValue(DATE_INFO_MAP.get(k).apply(monitoringDateInfoVo))
                    .build();
            indexRealtimeYqs.add(industryIndexRealtimeYq);
        });

        //更新底座数据表
        monitoringService.updateIndustryIndex(indexRealtimeYqs);
        monthMonitoringCache.put(MONITORING_MONTH_CACHE, monitoringMonthInfoVo);
        dateMonitoringCache.put(MONITORING_DATE_CACHE, monitoringDateInfoVo);
    }


    /**
     * 异步同步数据到底座指标表
     *
     * @param indexRealtimeYqs
     */
    @Async
    public void updateIndustryIndex(List<IndustryIndexRealtimeYq> indexRealtimeYqs) {
        industryIndexRealtimeYqService.updateValueByName(indexRealtimeYqs);
    }

    /**
     * 按传入字段统计
     *
     * @param monitoringList
     * @param function
     * @return
     */
    private Map<String, Integer> countByFunction(List<PublicOpinionMonitoring> monitoringList,
                                                 Function<PublicOpinionMonitoring, String> function) {
        return monitoringList.stream()
                .collect(Collectors.toMap(function, o -> 1, Integer::sum));
    }


    /**
     * 统计map转为返回对象
     *
     * @param map
     * @return
     */
    private List<RankVO> mapToRankVo(Map<String, Integer> map, int limitSize) {
        AtomicInteger index = new AtomicInteger(0);
        return map.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed()).map(entry -> {
                    int currentIndex = index.getAndIncrement();
                    return new RankVO(entry.getKey(), entry.getValue().toString(), currentIndex + 1);
                })
                .limit(limitSize)
                .collect(Collectors.toList());
    }


    private Date addDays(Date date, int dayNum) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, dayNum);
        date = calendar.getTime();
        return date;
    }


    private static String convertToString(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof List) {
            return JSON.toJSONString(obj);
        } else if (obj instanceof Integer || obj instanceof Double) {
            return String.valueOf(obj);
        } else if (obj instanceof String) {
            return (String) obj;
        } else {
            return "";
        }
    }


}
